Getting World and Claim Y Dimensions

This API allows you to query the vertical (Y-axis) boundaries for worlds and claims. This is particularly useful for extensions that need to understand claim boundaries for different claim types.

== World Y Boundaries ==

GriefPrevention.getWorldMinY(world) - gets the minimum Y coordinate (build limit) for the world
GriefPrevention.getWorldMaxY(world) - gets the maximum Y coordinate (build limit) for the world

Example:
    World world = player.getWorld();
    int minY = GriefPrevention.getWorldMinY(world);  // e.g., -64 in 1.18+
    int maxY = GriefPrevention.getWorldMaxY(world);  // e.g., 320 in 1.18+

== Claim Y Boundaries ==

claim.getMinY() - gets the minimum Y coordinate for the claim
claim.getMaxY() - gets the maximum Y coordinate for the claim
claim.getYHeight() - gets the vertical height of the claim in blocks
claim.containsY(y) - checks if a Y coordinate is within the claim's boundaries
claim.is3D() - checks if this is a 3D claim (respects custom Y boundaries)

Behavior by claim type:
- Main Claims: Always span world min to max Y (full height)
- Admin Claims: Always span world min to max Y (full height)
- 2D Subdivisions: Span world min to max Y (inherit from world)
- 3D Subdivisions: Use their own defined Y boundaries

Example:
    Claim claim = GriefPrevention.instance.dataStore.getClaimAt(location, true, null);
    if (claim != null) {
        int minY = claim.getMinY();
        int maxY = claim.getMaxY();
        int height = claim.getYHeight();
        boolean is3D = claim.is3D();
        
        if (is3D) {
            // This claim has custom Y boundaries
            player.sendMessage("This 3D claim spans Y " + minY + " to " + maxY);
        } else {
            // This claim spans the full world height
            player.sendMessage("This claim spans the full world height");
        }
    }

== Detailed Claim Y Information ==

For more detailed information, use the ClaimYInfo class:

claim.getYInfo() - returns a ClaimYInfo object with comprehensive Y boundary data

ClaimYInfo methods:
- getMinY() - minimum Y coordinate
- getMaxY() - maximum Y coordinate  
- getHeight() - vertical height in blocks
- is3D() - true if claim has custom Y boundaries
- isSubdivision() - true if this is a subdivision (has parent)
- isAdminClaim() - true if this is an admin claim
- getClaimType() - human-readable type: "Main Claim", "Admin Claim", "2D Subdivision", "3D Subdivision", "Admin 2D Subdivision", "Admin 3D Subdivision"

Example:
    Claim claim = GriefPrevention.instance.dataStore.getClaimAt(location, true, null);
    if (claim != null) {
        Claim.ClaimYInfo yInfo = claim.getYInfo();
        
        player.sendMessage("Claim Type: " + yInfo.getClaimType());
        player.sendMessage("Y Range: " + yInfo.getMinY() + " to " + yInfo.getMaxY());
        player.sendMessage("Height: " + yInfo.getHeight() + " blocks");
        
        if (yInfo.is3D()) {
            player.sendMessage("This is a 3D claim with custom vertical boundaries.");
        }
        
        // toString() provides a summary
        System.out.println(yInfo.toString());
        // Output: ClaimYInfo{type=3D Subdivision, minY=60, maxY=80, height=21}
    }

== Checking Y Containment ==

claim.containsY(y) - checks if a Y coordinate is within the claim's Y boundaries

For 3D claims, this checks against the actual Y boundaries.
For non-3D claims, this always returns true (Y is not enforced).

Example:
    int blockY = block.getY();
    if (claim.containsY(blockY)) {
        // The Y coordinate is within this claim's vertical boundaries
    }


Boundary Visualization API
==========================

As of v18.0.3, the boundary-visualization extension points originally developed on the `feature/gpapiaddon-workspace` branch are part of the supported public API. Addons can now:

- Trigger the same visuals GriefPrevention uses internally (claim outlines, conflict zones, nearby-claim sweeps).
- Listen for visualization events to mutate, replace, or cancel visuals before they are drawn.
- Provide entirely custom rendering pipelines (particles, BlockDisplay glow, packets, etc.) by implementing the supplied interfaces.

The relevant packages are:

- com.griefprevention.visualization        - rendering surface (Boundary, BoundaryVisualization, VisualizationStyle, VisualizationProvider, BlockBoundaryRenderer, VisualizationType)
- com.griefprevention.visualization.impl   - built-in providers (FakeBlockVisualization, LegacyFakeBlockVisualization, AntiCheatCompatVisualization, GlowingVisualization)
- com.griefprevention.events               - listenable events (BoundaryVisualizationEvent)

== Triggering a Visualization ==

The static helpers on BoundaryVisualization fire a BoundaryVisualizationEvent and apply the resulting visualization to the player. They are safe to call from main-thread code.

BoundaryVisualization.visualizeClaim(player, claim, type)
BoundaryVisualization.visualizeClaim(player, claim, type, block)
BoundaryVisualization.visualizeArea(player, boundingBox, type)
BoundaryVisualization.visualizeArea(player, boundingBox, type, height)
BoundaryVisualization.visualizeNearbyClaims(player, claims, height)

`type` is a VisualizationType:
    CLAIM, ADMIN_CLAIM, SUBDIVISION, SUBDIVISION_3D, ADMIN_CLAIM_3D,
    CONFLICT_ZONE, CONFLICT_ZONE_3D, INITIALIZE_ZONE, INITIALIZE_ZONE_3D

Example - show a player their claim outline at eye level:
    Claim claim = GriefPrevention.instance.dataStore.getClaimAt(player.getLocation(), true, null);
    if (claim != null) {
        VisualizationType type = claim.isAdminClaim()
                ? VisualizationType.ADMIN_CLAIM
                : VisualizationType.CLAIM;
        BoundaryVisualization.visualizeClaim(player, claim, type);
    }

Example - flash a conflict zone for a pending claim region:
    BoundingBox box = new BoundingBox(x1, y1, z1, x2, y2, z2);
    BoundaryVisualization.visualizeArea(player, box, VisualizationType.CONFLICT_ZONE);

Example - visualize every claim near a moderator:
    Set<Claim> nearby = GriefPrevention.instance.dataStore.getNearbyClaims(player.getLocation());
    BoundaryVisualization.visualizeNearbyClaims(player, nearby, player.getEyeLocation().getBlockY());

== Listening for BoundaryVisualizationEvent ==

BoundaryVisualizationEvent is a standard Bukkit PlayerEvent. The boundary collection is mutable, and the active VisualizationProvider can be swapped before the visualization is constructed.

Use cases:
- Add custom decorative boundaries (e.g. waypoints).
- Strip boundaries from claims your addon hides.
- Replace the default fake-block renderer with your own provider for specific players or regions.

Example - swap to a custom particle-based provider for one player:
    @EventHandler
    public void onVisualize(BoundaryVisualizationEvent event) {
        if (!event.getPlayer().hasPermission("myaddon.fancy")) return;
        event.setProvider((world, visualizeFrom, height) ->
                new MyParticleVisualization(world, visualizeFrom, height));
    }

Example - hide boundaries that belong to admin claims for a specific player:
    @EventHandler
    public void onVisualize(BoundaryVisualizationEvent event) {
        if (!event.getPlayer().hasPermission("myaddon.hideadmin")) return;
        event.getBoundaries().removeIf(b -> b.claim() != null && b.claim().isAdminClaim());
    }

== Implementing a Custom VisualizationProvider ==

A VisualizationProvider is a factory for BoundaryVisualization instances. The simplest path is to extend one of the built-in classes:

- BlockBoundaryVisualization / FakeBlockVisualization - 1.13+ fake-block outlines.
- LegacyFakeBlockVisualization                        - 1.8.8 NMS packet path.
- AntiCheatCompatVisualization                        - 1.13+ outlines that respect anticheat transparency rules.
- GlowingVisualization                                - 1.19.3+ BlockDisplay glow outlines.

Example - a custom provider that always uses glow on supported servers:
    public final class GlowOnlyProvider implements VisualizationProvider {
        @Override
        public @NotNull BoundaryVisualization create(
                @NotNull World world,
                @NotNull IntVector visualizeFrom,
                int height) {
            return new GlowingVisualization(world, visualizeFrom, height);
        }
    }

Register it per-call via the event:
    event.setProvider(new GlowOnlyProvider());

== Defining a Custom VisualizationStyle ==

VisualizationStyle is a key-addressable variant on top of VisualizationType. Built-in types are themselves styles. Addons may register their own styles for use with `BoundaryVisualization.visualizeClaim(player, claim, style)` and friends.

Implement the interface and (optionally) a BlockBoundaryRenderer if you want the built-in fake-block path to render your style:

    public final class MyClaimStyle implements VisualizationStyle {
        @Override public @NotNull String getKey() { return "myaddon:claim"; }
        @Override public @Nullable BlockBoundaryRenderer getBlockRenderer() {
            return MY_RENDERER; // or null to require a custom VisualizationProvider
        }
    }

Note: The keys intentionally use a plain `namespace:name` string instead of Bukkit's NamespacedKey so the same jar continues to load on legacy servers (1.8.8) where NamespacedKey does not exist.
